(function ($) {
  var FilterSelect = function (el, options) {
    this.options = options;
    this.$el = $(el);
    this.uuid = _.random(0, 999999);

    this.init();
  };
  FilterSelect.prototype = {
    init: function () {
      this.query = '';
      this.filterData = [];
      this.initContainer();
      this.initSelect();
      this.bindEvent();
      this.initDefaultData();
    },

    initContainer: function () {
      var $el = this.$el;
      var $container = $('<div class="filter-select"></div>');
      $el.html($container);
      this.$container = $container;
    },

    initSelect: function () {
      var $container = this.$container;
      var options = this.options;

      var $input = $(`<input class="filter-select-input" placeholder="${options.placeholder}">`);
      $container.append($input);
      this.$input = $input;

      var $btn = $('<div class="filter-select-btn"><span>&#9660;</span></div>');
      $container.append($btn);
      this.$btn = $btn;
      $btn.on('click', function () {
        $input.click();
      });

      var $dropdown = $(`<div id="dropdown-${this.uuid}" class="filter-select-dropdown"></div>`);

      this.$dropdown = $dropdown;
      this.setDropdownData();

      $('body').append($dropdown);
      this.isShow = false;

      this.triggerEvent();
    },

    triggerEvent: function () {
      var $input = this.$input;
      var flag = false;
      $input.on('compositionstart', function () {
        flag = true;
      });

      $input.on('compositionend', function () {
        flag = false;
        $(this).trigger('filter-input');
      });

      $input.on('input', function () {
        if (flag) {
          return;
        }
        $(this).trigger('filter-input');
      });

      $input.on('change', function (event) {
        event.stopPropagation();
      });
    },

    setDropdownData: function () {
      var $dropdown = this.$dropdown;
      var options = this.options,
        valueField = options.valueField,
        labelField = options.labelField;
      var filterData = this.queryData();
      this.filterData = filterData;

      $dropdown.html('');
      if (filterData.length === 0) {
        this.setEmptyDataHtml();
        return;
      }

      var selectItem = this.selectItem;
      for (var i = 0; i < filterData.length; i++) {
        var d = filterData[i];
        var $option = $(`<div class="dropdown-item">${d[labelField]}</div>`);
        $option.data('item', d);
        if (selectItem !== undefined) {
          if (selectItem[valueField] === d[valueField]) {
            $option.addClass('select');
          }
        }
        $dropdown.append($option);
      }
    },

    queryData: function () {
      var options = this.options,
        data = options.data,
        filterMethod = options.filterMethod,
        labelField = options.labelField;
      var query = this.query;

      if (query === '') {
        return data;
      }
      data = _.filter(data, function (d) {
        if (filterMethod !== null) {
          return filterMethod(d);
        }

        var value = d[labelField];
        return value.indexOf(query) !== -1;
      });
      return data;
    },

    setEmptyDataHtml: function () {
      var $dropdown = this.$dropdown;
      var options = this.options,
        data = options.data;

      var text = '';
      if (data.length === 0) {
        text = options.dataEmptyText;
      } else {
        text = options.filterEmptyText;
      }

      var $empty = $(`<div class="empty-text">${text}</div>`);
      $dropdown.append($empty);
    },

    initDefaultData: function () {
      var defaultValue = this.options.defaultValue;
      this.setValue(defaultValue);
    },

    showDropdown: function () {
      var options = this.options;
      var $dropdown = this.$dropdown;
      var $input = this.$input;
      var $btn = this.$btn;
      this.resetDropdownPostion();
      $dropdown.show();
      this.isShow = true;
      this.scrollDropdown();

      var self = this;
      $(document).off('mouseup');
      $(document).on('mouseup', function (event) {
        var $target = $(event.target);
        if ($target.is($input) || $target.is($btn) || $target.is($btn.find('*'))) {
          $(document).off('mouseup');
          return;
        }

        var flag = $target.is(function () {
          var $this = $(this);
          if ($this.is($dropdown)) {
            return true;
          }

          var $items = $dropdown.find('*');
          for (var i = 0; i < $items.length; i++) {
            var $item = $($items[i]);
            if ($this.is($item)) {
              return true;
            }
          }
          return false;
        });
        if (flag) {
          return;
        }

        self.hideDropdown();
        $(document).off('mouseup');
      });

      this.$el.trigger('show');
    },

    resetDropdownPostion: function () {
      var $dropdown = this.$dropdown;
      var $input = this.$input;
      var totalHeight = document.documentElement.offsetHeight || document.body.offsetHeight;
      var top = $input.offset().top;
      var left = $input.offset().left;
      var height = $input.outerHeight();
      var dropdownHeight = $dropdown.outerHeight();
      if (top + height + dropdownHeight > totalHeight) {
        top -= $dropdown.outerHeight();
        $dropdown.removeClass('postion-bottom').addClass('postion-top');
      } else {
        top += height;
        $dropdown.removeClass('postion-top').addClass('postion-bottom');
      }

      var width = $input.outerWidth();
      $dropdown.width(width - 2);

      $dropdown.css({
        top: top,
        left: left
      });
    },

    scrollDropdown: function () {
      var $dropdown = this.$dropdown;
      var $select = $dropdown.find('.dropdown-item.select');
      if ($select.length === 0) {
        return;
      }

      var index = $select.index();
      index -= 2;
      if (index < 0) {
        index = 0;
      }
      $dropdown.scrollTop(index * 30);
    },

    hideDropdown: function () {
      var $dropdown = this.$dropdown;
      $dropdown.hide();
      this.isShow = false;

      if (this.query !== '') {
        this.query = '';
        this.setDropdownData();
      }
      this.$el.trigger('hide');
    },

    toggleDropdown: function () {
      var isShow = this.isShow;
      isShow = !isShow;
      if (isShow) {
        this.showDropdown();
      } else {
        this.hideDropdown();
      }
    },

    bindEvent: function () {
      var $dropdown = this.$dropdown;
      var $input = this.$input;
      var options = this.options;
      var self = this;

      $input.on('click', function () {
        self.toggleDropdown();
      });

      $input.on('filter-input', function () {
        var value = $(this).val();
        self.query = value;
        self.setDropdownData();

        if (!self.isShow) {
          self.showDropdown();
        } else {
          self.resetDropdownPostion();
        }
      });

      $input.on('change', function () {
        var selectItem = self.selectItem;
        var value;
        if (selectItem === undefined) {
          value = '';
        } else {
          value = selectItem[options.labelField];
        }
        $(this).val(value);
      });

      $dropdown.on('click', '.dropdown-item', function () {
        var $select = $dropdown.find('.dropdown-item.select');
        var $this = $(this);
        if ($select.is($this)) {
          self.hideDropdown();
          $(document).off('mouseup');
          return;
        }

        var selectItem = $this.data('item');
        self.selectItem = selectItem;
        $input.val(selectItem[options.labelField]);
        $select.removeClass('select');
        $this.addClass('select');
        self.hideDropdown();
        $(document).off('mouseup');
        self.$el.trigger('change', selectItem[options.valueField], selectItem);
      });

      var $scrollEl = this.getScrollEl();
      if ($scrollEl !== null) {
        $scrollEl.off('scroll');
        $scrollEl.on('scroll', function () {
          self.resetDropdownPostion();
        });
        this.$scrollEl = $scrollEl;
      }
    },

    getScrollEl: function () {
      var $el = this.$el;
      var $parent = $el.parent();

      while (true) {
        var scrollY = $parent.css('overflow-y');
        if (scrollY === 'auto' || scrollY === 'scroll') {
          return $parent;
        }

        var scrollX = $parent.css('overflow-x');
        if (scrollX === 'auto' || scrollX === 'scroll') {
          return $parent;
        }

        $parent = $parent.parent();
        if ($parent.length === 0) {
          break;
        }

        if ($parent.is(document)) {
          break;
        }
      }

      return null;
    },

    setValue: function (value) {
      if (value === null) {
        return;
      }
      var options = this.options,
        data = options.data,
        valueField = options.valueField,
        labelField = options.labelField;
      var $input = this.$input;
      var $dropdown = this.$dropdown;

      var selectItem = _.find(data, function (d) {
        return d[valueField] === value;
      });
      if (selectItem === undefined) {
        this.selectItem = undefined;
        $input.val('');
        return;
      }

      if (this.selectItem !== undefined && this.selectItem[valueField] === selectItem[valueField]) {
        return;
      }

      this.selectItem = selectItem;
      $input.val(selectItem[labelField]);

      var $items = $dropdown.find('.dropdown-item');
      $items.removeClass('select');
      for (var i = 0; i < $items.length; i++) {
        var $item = $($items[i]);
        var item = $item.data('item');
        if (item[valueField] === selectItem[valueField]) {
          $item.addClass('select');
          break;
        }
      }
    },

    getValue: function () {
      var options = this.options;
      var selectItem = this.selectItem;
      if (selectItem === undefined) {
        return '';
      }
      return selectItem[options.valueField];
    },

    getValueObj: function () {
      var selectItem = this.selectItem;
      if (selectItem === undefined) {
        return null;
      }
      return selectItem;
    },

    setData: function (data) {
      var options = this.options,
        selectItem = this.selectItem,
        valueField = options.valueField;

      if (selectItem !== undefined) {
        var item = _.find(data, function (d) {
          return d[valueField] === selectItem[valueField];
        });
        if (item === undefined) {
          this.selectItem = undefined;
          this.$input.val('');
        }
      }

      this.options.data = data;
      this.setDropdownData();
    },

    destroy: function () {
      this.$el.html('');
      this.$dropdown.remove();
      var $scrollEl = this.$scrollEl;
      if ($scrollEl !== undefined) {
        $scrollEl.off('scroll');
      }
    }
  };

  FilterSelect.DEFAULTS = {
    data: [],
    defaultValue: null,
    valueField: 'value',
    labelField: 'label',
    filterMethod: null,
    filterEmptyText: '无符合条件数据',
    dataEmptyText: '无数据',
    placeholder: '请选择'
  };

  var allowedMethods = ["setValue", "setData", "getValue", "getValueObj", "showDropdown", "hideDropdown", "destroy"];
  $.fn.filterSelect = function (option) {
    var value, args = Array.prototype.slice.call(arguments, 1);
    if (option === undefined) {
      option = {};
    }

    this.each(function () {
      var $this = $(this),
        data = $this.data('filter.select'),
        options = $.extend({}, FilterSelect.DEFAULTS, $this.data(), typeof option === 'object' && option);

      if (typeof option === 'string') {
        if ($.inArray(option, allowedMethods) < 0) {
          throw new Error("Unknown method: " + option);
        }

        if (!data) {
          return;
        }

        value = data[option].apply(data, args);

        if (option === 'destroy') {
          $this.removeData('filter.select');
        }
      } else {
        if (!data) {
          data = new FilterSelect(this, options);
          $this.data('filter.select', data);
        }
      }
    });
    return typeof value === 'undefined' ? this : value;
  };
  $.fn.filterSelect.Constructor = FilterSelect;
  $.fn.filterSelect.defaults = FilterSelect.DEFAULTS;
  $.fn.filterSelect.methods = allowedMethods;
})(jQuery);